/**
 * 
 */
package business.user;

import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import security.SecurityRole;
import service.IServiceManager;
import service.Service;
import service.Service.ServiceType;
import service.ServiceManager;
import service.exception.ServiceException;
import service.resources.Dao;
import bean.Customer;
import bean.ParamBean;
import bean.PwdChange;
import bean.User;
import bean.UserProfile;
import business.exception.ChangeCustomerProfileException;
import business.exception.ChangePwdException;
import business.exception.FindAllUsersException;
import business.exception.UserRegistrationException;

import common.Utils;

import data.DaoFactory.DaoName;
import data.ICustomerDao;
import data.IUserDao;
import data.Pagination;
import data.exception.DaoException;

/**
 * Manage users.
 * 
 * @author chuxiaoyuan
 *
 */
public class UserManagement extends ServiceManager {

	static {
		REGISTER(UserManagement.class);
	}

	/**
	 * Constructor.
	 * 
	 * @param request
	 *            the HTTP servlet request.
	 */
	public UserManagement(HttpServletRequest request) {
		super(request);
	}
	
	/**
	 * The User DAO.
	 */
	@Dao(name = DaoName.UserDao)
	private IUserDao userDao;

	/**
	 * The Customer DAO.
	 */
	@Dao(name = DaoName.CustomerDao)
	private ICustomerDao customerDao;

	/**
	 * Register Customer service.
	 */
	public static final String SERVICE_REGISTER_CUSTOMER = "REGISTER_CUSTOMER";

	/**
	 * Register Administrator service.
	 */
	public static final String SERVICE_REGISTER_ADMIN = "REGISTER_ADMIN";

	/**
	 * Change password service.
	 */
	public static final String SERVICE_CHANGE_PWD = "CHANGE_PWD";

	/**
	 * Change customer profile.
	 */
	public static final String SERVICE_CHANGE_CUSTOMER_PROFILE = "CHANGE_CUSTOMER_PROFILE";
	
	/**
	 * Find all users service.
	 */
	public static final String SERVICE_FIND_ALL_USERS = "FIND_ALL_USERS";
	
	/**
	 * Find all users service.
	 */
	public static final String SERVICE_FIND_ALL_USERS_WITH_PAGINATION = "FIND_ALL_USERS_WITH_PAGINATION";
	
	/**
	 * Register a new customer.
	 * 
	 * @param profile the customer profile. The id fields will be ignored.
	 * @return the new registered customer. Id fields will be populated.
	 */
	@Service(name = SERVICE_REGISTER_CUSTOMER, type = ServiceType.Transaction)
	public UserProfile registerCustomer(UserProfile profile)
			throws UserRegistrationException {
		
		// Get user register information
		User userInfo = profile.getUserInfo();
		Customer customerInfo = profile.getCustomerInfo();
		if (userInfo == null || customerInfo == null) {
			
			String msg = "Incomplete customer registeration information.";
			//this.getLogger().error(msg);
			throw new UserRegistrationException(msg);
		}
		
		// Validate username and password
		String username = profile.getUsername();
		String password = profile.getPassword();
		if (!Utils.validateEmailAddress(username)) {

			String msg = "Invalid user email address for customer registeration.";
			//this.getLogger().error(msg);
			throw new UserRegistrationException(msg);
		}
		if (!Utils.validatePassword(password)) {

			String msg = "Invalid password for customer registeration.";
			//this.getLogger().error(msg);
			throw new UserRegistrationException(msg);
		}
		
		// Encrypt password
		String encryptedPwd;
		try {
			encryptedPwd = Utils.encryptPassword(password);
			
		} catch (NoSuchAlgorithmException e) {
			
			String msg = "Password encryotion fail for customer registeration.";
			//this.getLogger().error(msg);
			throw new UserRegistrationException(msg, e);
			
		} catch (UnsupportedEncodingException e) {

			String msg = "Password encryotion fail for customer registeration.";
			//this.getLogger().error(msg);
			throw new UserRegistrationException(msg, e);
		}
		userInfo.setPassword(encryptedPwd);
		
		// Create customer information
		try {
			customerDao.insert(customerInfo);

		} catch (DaoException e) {

			String msg = "Failed to register new customer: insert customer info error.";
			//this.getLogger().error(msg);
			throw new UserRegistrationException(msg, e);
		}
		
		// Create user information
		Long customerId = customerInfo.getId();
		userInfo.setCustomerid(customerId);
		try {
			userDao.insert(userInfo);
			
		} catch (DaoException e) {

			String msg = "Failed to register new customer: insert user info error.";
			//this.getLogger().error(msg);
			throw new UserRegistrationException(msg, e);
		}
		
		assert(profile.isCustomer());
		return profile;
	}

	/**
	 * Register a new administrator.
	 * Only administrators can register new administrator.
	 * 
	 * @param profile the admin profile. The id fields will be ignored.
	 * @return the new registered admin. Id fields will be populated.
	 */
	@Service(name = SERVICE_REGISTER_ADMIN, 
			type = ServiceType.Transaction, security=SecurityRole.Admin)
	public UserProfile registerAdmin(UserProfile profile) 
			throws UserRegistrationException {

		// Get user register information
		User userInfo = profile.getUserInfo();
		if (userInfo == null) {
			
			String msg = "Incomplete admin registeration information.";
			//this.getLogger().error(msg);
			throw new UserRegistrationException(msg);
		}
		if (!profile.setCustomerInfo(null)) {

			String msg = "Invalid admin registeration information.";
			//this.getLogger().error(msg);
			throw new UserRegistrationException(msg);
		}
		userInfo.setCustomerid(null);
		
		// Validate username and password
		String username = profile.getUsername();
		String password = profile.getPassword();
		if (!Utils.validateEmailAddress(username)) {

			String msg = "Invalid user email address for admin registeration.";
			//this.getLogger().error(msg);
			throw new UserRegistrationException(msg);
		}
		if (!Utils.validatePassword(password)) {

			String msg = "Invalid password for admin registeration.";
			//this.getLogger().error(msg);
			throw new UserRegistrationException(msg);
		}
		
		// Encrypt password
		String encryptedPwd;
		try {
			encryptedPwd = Utils.encryptPassword(password);
			
		} catch (NoSuchAlgorithmException e) {
			
			String msg = "Password encryotion fail for admin registeration.";
			//this.getLogger().error(msg);
			throw new UserRegistrationException(msg, e);
			
		} catch (UnsupportedEncodingException e) {

			String msg = "Password encryotion fail for admin registeration.";
			//this.getLogger().error(msg);
			throw new UserRegistrationException(msg, e);
		}
		userInfo.setPassword(encryptedPwd);
		
		// Create user information
		try {
			userDao.insert(userInfo);
			
		} catch (DaoException e) {

			String msg = "Failed to register new admin: insert user info error.";
			//this.getLogger().error(msg);
			throw new UserRegistrationException(msg, e);
		}
		
		assert(profile.isAdmin());
		return profile;
	}
	
	/**
	 * Change current user's password.
	 * 
	 * If password changed successfully, the user will be <b>automatically
	 * logout</b> to allow him immediately login using the new password.
	 * 
	 * @param pwdChange the old and new password.
	 */
	@Service(name = SERVICE_CHANGE_PWD, 
			type = ServiceType.Transaction, security=SecurityRole.All)
	public void changePassword(PwdChange pwdChange) throws ChangePwdException {
		
		// Validate input
		String oldPwd = pwdChange.getOldPassword();
		String newPwd = pwdChange.getNewPassword();
		if (oldPwd == null || newPwd == null || oldPwd.endsWith(newPwd)) {

			String msg = "Failed to change password: invalid old or new password.";
			//this.getLogger().error(msg);
			throw new ChangePwdException(msg);
		}
		
		// Validate new password
		if (!Utils.validatePassword(newPwd)) {

			String msg = "Failed to change password: invalid new password.";
			//this.getLogger().error(msg);
			throw new ChangePwdException(msg);
		}
		
		// Get the current user information.
		ParamBean param = this.getParamBean();
		if (param == null) {
			String msg = "Failed to change password: current use not valid.";
			//this.getLogger().error(msg);
			throw new ChangePwdException(msg);
		}
		UserProfile profile = param.getUserProfile();
		
		// Validate user using old password
		String encryptedPwd = null;
		try {
			encryptedPwd = Utils.encryptPassword(oldPwd);
			
		} catch (NoSuchAlgorithmException e) {
			
			String msg = "Password encryotion fail for password changing.";
			//this.getLogger().error(msg);
			throw new ChangePwdException(msg, e);
			
		} catch (UnsupportedEncodingException e) {

			String msg = "Password encryotion fail for password changing.";
			//this.getLogger().error(msg);
			throw new ChangePwdException(msg, e);
		}
		List<User> users;
		try {
			// Select by old encrypted password
			users = userDao.selectByAccountAndPassword(
					profile.getUsername(), encryptedPwd);
			
		} catch (DaoException e) {
			
			String msg = "Failed to change password: validate old password error.";
			//this.getLogger().error(msg);
			throw new ChangePwdException(msg, e);
		}
		if (users.size() != 1) {

			String msg = "Failed to change password: old password wrong";
			//this.getLogger().error(msg);
			throw new ChangePwdException(msg);
		}
		
		// Change to new password.
		try {
			encryptedPwd = Utils.encryptPassword(newPwd);
			
		} catch (NoSuchAlgorithmException e) {
			
			String msg = "Password encryotion fail for password changing.";
			//this.getLogger().error(msg);
			throw new ChangePwdException(msg, e);
			
		} catch (UnsupportedEncodingException e) {

			String msg = "Password encryotion fail for password changing.";
			//this.getLogger().error(msg);
			throw new ChangePwdException(msg, e);
		}
		// Set the encrypted new password and update.
		User user = profile.getUserInfo();
		user.setPassword(encryptedPwd);
		try {
			userDao.updatePassword(user);
			
		} catch (DaoException e) {
			String msg = "Failed to change password: password update failed.";
			//this.getLogger().error(msg);
			throw new ChangePwdException(msg, e);
		}
		
		// Password change successfully
		this.getLogger().log("Password changed successfully.");
		
		// Logout current user.
		IServiceManager authentication = new UserAuthentication(this.getRequest());
		try {
			// Execute logout service.
			authentication.execute(UserAuthentication.SERVICE_LOGOUT);
			
		} catch (ServiceException e) {

			String msg = "Logout failed after changing password.";
			this.getLogger().log(msg);
			// Go on anyway.
		}
	}
	
	
	@Service(name = SERVICE_CHANGE_CUSTOMER_PROFILE,
			type = ServiceType.Transaction, security = SecurityRole.Customer)
	public void changeCustomerProfile(Customer customerInfo) 
			throws ChangeCustomerProfileException {

		// Validate input
		if (customerInfo.getName() == null) {
			String msg = "Failed to change customer profile: name cannot be empty.";
			throw new ChangeCustomerProfileException(msg);
		}
		
		// Get the current user information.
		ParamBean param = this.getParamBean();
		if (param == null) {
			String msg = "Failed to customer profile: current use not valid.";
			//this.getLogger().error(msg);
			throw new ChangeCustomerProfileException(msg);
		}
		UserProfile profile = param.getUserProfile();
		if (!profile.getCustomerid().equals(customerInfo.getId())) {

			String msg = "Failed to customer profile: this is not the current login user.";
			//this.getLogger().error(msg);
			throw new ChangeCustomerProfileException(msg);
		}
		
		// Update profile
		try {
			customerDao.update(customerInfo);
			
		} catch (DaoException e) {

			String msg = "Failed to customer profile: update error.";
			//this.getLogger().error(msg);
			throw new ChangeCustomerProfileException(msg, e);
		}
	}

	/**
	 * Find all users.
	 * 
	 * @return A list of all user profiles.
	 */
	@Service(name = SERVICE_FIND_ALL_USERS, 
			type = ServiceType.Query, security = SecurityRole.Admin)
	public List<UserProfile> findAllUsers() throws FindAllUsersException {
		
		// Find all users without pagination
		return this.findAllUsers(null);
	}

	/**
	 * Find all users with pagination.
	 * 
	 * @return A list of all user profiles.
	 */
	@Service(name = SERVICE_FIND_ALL_USERS_WITH_PAGINATION, 
			type = ServiceType.Query, security = SecurityRole.Admin)
	public List<UserProfile> findAllUsers(
			Pagination pagination) throws FindAllUsersException {

		List<UserProfile> profiles = new ArrayList<UserProfile>();
		
		// Get all users.
		List<User> users = null;
		try {
			if (pagination == null) {
				
				// Select all
				users = userDao.selectAll();
			
			} else {
				// Select with pagination
				users = userDao.selectAll(pagination);
			}
			
		} catch (DaoException e) {
			
			String msg = "Failed to select all users.";
			//this.getLogger().error(msg);
			throw new FindAllUsersException(msg, e);
		}
		
		// Populate results
		for (User user : users) {
			UserProfile profile = null;
			if (user.getCustomerid() == null) {
				// Administrator.
				profile = new UserProfile(user);
			
			} else {
				// Customer
				Customer customerInfo;
				try {
					customerInfo = customerDao.selectById(
							user.getCustomerid());
					
				} catch (DaoException e) {

					String msg = 
							"Failed to get customer infor for user: " 
									+ user.getAccount();
					//this.getLogger().error(msg);
					throw new FindAllUsersException(msg, e);
				}
				profile = new UserProfile(user, customerInfo);
			}
			profiles.add(profile);
		}
		return profiles;
	}
}
